package org.bugeden.netdisk.vfs

import org.apache.commons.io.monitor.FileEntry
import java.util.Date
import org.apache.commons.io.IOUtils
import org.apache.commons.codec.digest.DigestUtils
import org.bugeden.netdisk.transfer.{Block, IDevice}
import org.bugeden.netdisk.common.Lang
import org.bugeden.netdisk.transfer.IDevice.UnExistBlockException
import org.bugeden.netdisk.json.{Json, BlockDescriptor, FileDescriptor}
import java.io.{FileOutputStream, FileInputStream, File}
import org.apache.commons.lang.StringUtils
import org.bugeden.netdisk.vfs.IDisk.{FileNotExistException, FileExistException}
import org.bugeden.netdisk.utils.PathUtils

/**
 * Created by IntelliJ IDEA.
 * User: inotseeyou
 * Date: 2010-11-7
 * Time: 13:13:08
 * To change this template use File | Settings | File Templates.
 */

/**
 * Created by IntelliJ IDEA.
 * User: gzhang
 * Date: 2010-6-19
 * Time: 0:52:37
 * To change this template use File | Settings | File Templates.
 */

class MDisk extends AnyRef with IDisk {
  import MDisk._
  private var _label: String = "default-label"

  private var device: IDevice = null

  def setDevice(dev: IDevice): Unit = {
    this.device = dev
  }

  def blockSize: Int = 1024 * 1024 //1024*1024byte = 1M

  override def copy(from: String, to: String): String = ""

  override def setLabel(label: String): Unit = {
    this._label = label
  }

  override def mkdirs(path: String): String = {
    val parentPath = PathUtils.parent(path)
    if (!device.isBlockExist(Block(parentPath))) {
      this.mkdirs(parentPath)
    } else {
      this.mkdir(path)
    }
  }

  @throws(classOf[FileNotExistException])
  override def mkdir(path: String): String = {
    val parentPath = PathUtils.parent(path)
    if(path == "/" && !device.isBlockExist(Block(path))){
      throw new FileExistException("[%s] has exist, can't not to create...".format(path))
    }
    val parentBlock = device.read(Block(parentPath)) match{
      case Some(b) => b
      case None => throw new FileNotExistException("[%s] not exist, can't mkdir [%s]".format(parentPath, path))
    }
    if(device.isBlockExist(Block(path))){
      throw new FileExistException("[%s] has exist, can't not to create...".format(path))
    }
    //update parent node
    val desc = Json.fromJson(parentBlock.desc, classOf[FileDescriptor])
    val block = BlockBuilder.emptyDirBlock(path)
    val newMembers = new Array[String](desc.members.length + 1)
    desc.members.copyToArray(newMembers, 1)
    newMembers(0) = block.id
    desc.members = newMembers
    device.update(parentBlock.desc(desc.toJson))
    device.write(block).id

  }

  override def create(filename: String, fileForCreate: File): String = {
    require(fileForCreate.exists, "the file[%s] you create not exist a real one on local...".format(fileForCreate.getAbsolutePath))
    val parentPath = PathUtils.parent(filename)
    if (!device.isBlockExist(Block(parentPath))) {
      throw new FileNotExistException("[%s] not exist, can't create file[%s]".format(parentPath, filename))
    }
    if (device.isBlockExist(Block(filename))) {
      throw new FileExistException
    }

    def buildFileBlock(subIds: Array[String]): Block = {
      val desc = extractFileDescriptor(fileForCreate)
      val fileContent = subIds.mkString(",").getBytes
      val fileBlock = Block(fileContent).id(filename)
      desc.id = fileBlock.id
      desc.secId = fileBlock.secId
      desc.filename = filename
      desc.members = subIds
      fileBlock.desc(desc.toJson)
      fileBlock
    }
    val members = if (fileForCreate.isFile) {
      val blocks = this.fileToBlocks(fileForCreate)
      val ids = blocks.map(device.write(_)).map(_.id)
      ids
    } else {
      val sublist = fileForCreate.listFiles
      val ids = sublist.map(create(filename, _))
      ids
    }
    val fileBlock = buildFileBlock(members)
    device.write(fileBlock).id
  }

  private def fileToBlocks(file: File): Array[Block] = {
    require(file.isFile)
    val fis = new FileInputStream(file)
    try {
      val array = IOUtils.toByteArray(fis) //todo big file will occur some problem
      val bytesList = Lang.splitArray(array, this.blockSize)
      val blocks = new Array[Block](bytesList.length)
      for (i <- blocks.indices) {
        blocks(i) = new Block(bytesList(i))
        val b = blocks(i)
        val desc = BlockDescriptor(i)
        desc.id = b.id
        desc.secId = b.secId
        desc.size = b.data.size
        blocks(i).desc(desc.toJson)
      }
      blocks
    } finally {
      if (null != fis)
        fis.close
    }
  }

  override def label: String = "netdisk-1.0"

  override def move(from: String, to: String): String = {
    val fromBlock = device.read(Block(from)) match {
      case None => throw new FileExistException("file[%s] is not exist".format(from))
      case Some(en) => en
    }
    if (device.isBlockExist(Block(to))) {
      throw new FileExistException("file[%s] is exist".format(to))
    } else {
      device.write(fromBlock.id(to))
      device.remove(fromBlock.id(from))
    }
    to
  }

  override def update(filename: String, fileForUpdate: File): String = {
    try {
      //remove the exist
      this.delete(filename)
      //create the new one
      this.create(filename, fileForUpdate)
    } catch {
      case ex: UnExistBlockException =>
        this.create(filename, fileForUpdate)
    }
  }

  //todo make it can work on directory
  override def updateToLocalFile(filename: String, fileToUpdate: File): String = {
    val desc = this.fileDescriptor(filename)
    val bytes = desc.fileType match {
      case FileDescriptor.T_FILE => {
        val fos = new FileOutputStream(fileToUpdate.getAbsolutePath)
        try {
          val bytes = desc.members.foreach(id => {
            val block = device.read(Block(id)) match {
              case Some(b) => IOUtils.write(b.data, fos)
              case None => throw new Exception("Some block of file[%s] is missed".format(filename))
            }
          })
        } finally {
          if (null != fos)
            fos.close
        }
      }
      case FileDescriptor.T_DIR =>
        throw new UnsupportedOperationException("local directory not supported")
      case _@a => throw new Exception("unknown block type[%s]".format(a))
    }

    filename
  }

  override def delete(filename: String): String = {
    val block = device.read(Block(filename)) match{
      case Some(b) => b
      case None => return filename
    }
    val fdesc = Json.fromJson(block.desc, classOf[FileDescriptor])
    fdesc.fileType match {
      case FileDescriptor.T_FILE => device.remove(block)
      case FileDescriptor.T_DIR =>
        fdesc.members.foreach(this.delete(_))
        device.remove(block)
      case _ => throw new Exception("unknown Block type[%s]...".format(fdesc.fileType))
    }

    filename
  }

  override def clear: Unit = {
    throw new UnsupportedOperationException
  }

  override def list(path: String): Array[String] = {
    val desc = this.fileDescriptor(path)
    desc.fileType match {
      case FileDescriptor.T_DIR => desc.members
      case FileDescriptor.T_FILE => Array[String]()
      case _ => throw new Exception("unknown Block type[%s]...".format(desc.fileType))
    }
  }

  override def root() = "/"

  private def fileDescriptor(filename: String): FileDescriptor = {
    val block = device.read(Block(filename)) match{
      case Some(b) => b
      case None => throw new Exception("file[%s] is not exist...".format(filename))
    }
    Json.fromJson(block.desc, classOf[FileDescriptor])
  }
}

object MDisk {
  def extractFileDescriptor(file: File): FileDescriptor = {
    require(file.exists)
    val ret = new FileDescriptor
    val fileEntry = new FileEntry(file)
    ret.filename = file.getAbsolutePath
    ret.modifiedTime = new Date(fileEntry.getLastModified)
    ret.createdTime = ret.modifiedTime
    ret.fileType = if (file.isFile) FileDescriptor.T_FILE else FileDescriptor.T_DIR
    ret.size = fileEntry.getLength
    ret
  }

  object BlockBuilder {
    def emptyDirBlock(path: String): Block = {
      val block = Block(Array[Byte]())
      val desc = new FileDescriptor
      desc.fileType = FileDescriptor.T_DIR
      desc.members = Array[String]()
      desc.id = path
      desc.secId = block.secId
      block.desc(desc.toJson).id(path)
      block
    }
  }
}